/*!
 * jQuery Color Animations v2.0pre
 * http://jquery.org/
 *
 * Copyright 2011 John Resig
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 */

(function (jQuery, undefined) {
	var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color outlineColor".split(" "),

	// plusequals test for += 100 -= 100
		rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
	// a set of RE's that can match strings and generate color tuples.
		stringParsers = [{
			re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
			parse: function (execResult) {
				return [
					execResult[1],
					execResult[2],
					execResult[3],
					execResult[4]
				];
			}
		}, {
			re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
			parse: function (execResult) {
				return [
					2.55 * execResult[1],
					2.55 * execResult[2],
					2.55 * execResult[3],
					execResult[4]
				];
			}
		}, {
			re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,
			parse: function (execResult) {
				return [
					parseInt(execResult[1], 16),
					parseInt(execResult[2], 16),
					parseInt(execResult[3], 16)
				];
			}
		}, {
			re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,
			parse: function (execResult) {
				return [
					parseInt(execResult[1] + execResult[1], 16),
					parseInt(execResult[2] + execResult[2], 16),
					parseInt(execResult[3] + execResult[3], 16)
				];
			}
		}, {
			re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
			space: "hsla",
			parse: function (execResult) {
				return [
					execResult[1],
					execResult[2] / 100,
					execResult[3] / 100,
					execResult[4]
				];
			}
		}],

	// jQuery.Color( )
		color = jQuery.Color = function (color, green, blue, alpha) {
			return new jQuery.Color.fn.parse(color, green, blue, alpha);
		},
		spaces = {
			rgba: {
				cache: "_rgba",
				props: {
					red: {
						idx: 0,
						type: "byte",
						empty: true
					},
					green: {
						idx: 1,
						type: "byte",
						empty: true
					},
					blue: {
						idx: 2,
						type: "byte",
						empty: true
					},
					alpha: {
						idx: 3,
						type: "percent",
						def: 1
					}
				}
			},
			hsla: {
				cache: "_hsla",
				props: {
					hue: {
						idx: 0,
						type: "degrees",
						empty: true
					},
					saturation: {
						idx: 1,
						type: "percent",
						empty: true
					},
					lightness: {
						idx: 2,
						type: "percent",
						empty: true
					}
				}
			}
		},
		propTypes = {
			"byte": {
				floor: true,
				min: 0,
				max: 255
			},
			"percent": {
				min: 0,
				max: 1
			},
			"degrees": {
				mod: 360,
				floor: true
			}
		},
		rgbaspace = spaces.rgba.props,
		support = color.support = {},

	// colors = jQuery.Color.names
		colors,

	// local aliases of functions called often
		each = jQuery.each;

	spaces.hsla.props.alpha = rgbaspace.alpha;

	function clamp(value, prop, alwaysAllowEmpty) {
		var type = propTypes[prop.type] || {},
			allowEmpty = prop.empty || alwaysAllowEmpty;

		if (allowEmpty && value == null) {
			return null;
		}
		if (prop.def && value == null) {
			return prop.def;
		}
		if (type.floor) {
			value = ~~value;
		} else {
			value = parseFloat(value);
		}
		if (value == null || isNaN(value)) {
			return prop.def;
		}
		if (type.mod) {
			value = value % type.mod;
			// -10 -> 350
			return value < 0 ? type.mod + value : value;
		}

		// for now all property types without mod have min and max
		return type.min > value ? type.min : type.max < value ? type.max : value;
	}

	function stringParse(string) {
		var inst = color(),
			rgba = inst._rgba = [];

		string = string.toLowerCase();

		each(stringParsers, function (i, parser) {
			var match = parser.re.exec(string),
				values = match && parser.parse(match),
				parsed,
				spaceName = parser.space || "rgba",
				cache = spaces[spaceName].cache;


			if (values) {
				parsed = inst[spaceName](values);

				// if this was an rgba parse the assignment might happen twice
				// oh well....
				inst[cache] = parsed[cache];
				rgba = inst._rgba = parsed._rgba;

				// exit each( stringParsers ) here because we matched
				return false;
			}
		});

		// Found a stringParser that handled it
		if (rgba.length !== 0) {

			// if this came from a parsed string, force "transparent" when alpha is 0
			// chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
			if (Math.max.apply(Math, rgba) === 0) {
				jQuery.extend(rgba, colors.transparent);
			}
			return inst;
		}

		// named colors / default - filter back through parse function
		if (string = colors[string]) {
			return string;
		}
	}

	color.fn = color.prototype = {
		constructor: color,
		parse: function (red, green, blue, alpha) {
			if (red === undefined) {
				this._rgba = [null, null, null, null];
				return this;
			}
			if (red instanceof jQuery || red.nodeType) {
				red = red instanceof jQuery ? red.css(green) : jQuery(red).css(green);
				green = undefined;
			}

			var inst = this,
				type = jQuery.type(red),
				rgba = this._rgba = [],
				source;

			// more than 1 argument specified - assume ( red, green, blue, alpha )
			if (green !== undefined) {
				red = [red, green, blue, alpha];
				type = "array";
			}

			if (type === "string") {
				return this.parse(stringParse(red) || colors._default);
			}

			if (type === "array") {
				each(rgbaspace, function (key, prop) {
					rgba[prop.idx] = clamp(red[prop.idx], prop);
				});
				return this;
			}

			if (type === "object") {
				if (red instanceof color) {
					each(spaces, function (spaceName, space) {
						if (red[space.cache]) {
							inst[space.cache] = red[space.cache].slice();
						}
					});
				} else {
					each(spaces, function (spaceName, space) {
						each(space.props, function (key, prop) {
							var cache = space.cache;

							// if the cache doesn't exist, and we know how to convert
							if (!inst[cache] && space.to) {

								// if the value was null, we don't need to copy it
								// if the key was alpha, we don't need to copy it either
								if (red[key] == null || key === "alpha") {
									return;
								}
								inst[cache] = space.to(inst._rgba);
							}

							// this is the only case where we allow nulls for ALL properties.
							// call clamp with alwaysAllowEmpty
							inst[cache][prop.idx] = clamp(red[key], prop, true);
						});
					});
				}
				return this;
			}
		},
		is: function (compare) {
			var is = color(compare),
				same = true,
				myself = this;

			each(spaces, function (_, space) {
				var isCache = is[space.cache],
					localCache;
				if (isCache) {
					localCache = myself[space.cache] || space.to && space.to(myself._rgba) || [];
					each(space.props, function (_, prop) {
						if (isCache[prop.idx] != null) {
							same = ( isCache[prop.idx] === localCache[prop.idx] );
							return same;
						}
					});
				}
				return same;
			});
			return same;
		},
		_space: function () {
			var used = [],
				inst = this;
			each(spaces, function (spaceName, space) {
				if (inst[space.cache]) {
					used.push(spaceName);
				}
			});
			return used.pop();
		},
		transition: function (other, distance) {
			var end = color(other),
				spaceName = end._space(),
				space = spaces[spaceName],
				start = this[space.cache] || space.to(this._rgba),
				result = start.slice();

			end = end[space.cache];
			each(space.props, function (key, prop) {
				var index = prop.idx,
					startValue = start[index],
					endValue = end[index],
					type = propTypes[prop.type] || {};

				// if null, don't override start value
				if (endValue === null) {
					return;
				}
				// if null - use end
				if (startValue === null) {
					result[index] = endValue;
				} else {
					if (type.mod) {
						if (endValue - startValue > type.mod / 2) {
							startValue += type.mod;
						} else if (startValue - endValue > type.mod / 2) {
							startValue -= type.mod;
						}
					}
					result[prop.idx] = clamp(( endValue - startValue ) * distance + startValue, prop);
				}
			});
			return this[spaceName](result);
		},
		blend: function (opaque) {
			// if we are already opaque - return ourself
			if (this._rgba[3] === 1) {
				return this;
			}

			var rgb = this._rgba.slice(),
				a = rgb.pop(),
				blend = color(opaque)._rgba;

			return color(jQuery.map(rgb, function (v, i) {
				return ( 1 - a ) * blend[i] + a * v;
			}));
		},
		toRgbaString: function () {
			var prefix = "rgba(",
				rgba = jQuery.map(this._rgba, function (v, i) {
					return v == null ? ( i > 2 ? 1 : 0 ) : v;
				});

			if (rgba[3] === 1) {
				rgba.pop();
				prefix = "rgb(";
			}

			return prefix + rgba.join(",") + ")";
		},
		toHslaString: function () {
			var prefix = "hsla(",
				hsla = jQuery.map(this.hsla(), function (v, i) {
					if (v == null) {
						v = i > 2 ? 1 : 0;
					}

					// catch 1 and 2
					if (i && i < 3) {
						v = Math.round(v * 100) + "%";
					}
					return v;
				});

			if (hsla[3] === 1) {
				hsla.pop();
				prefix = "hsl(";
			}
			return prefix + hsla.join(",") + ")";
		},
		toHexString: function (includeAlpha) {
			var rgba = this._rgba.slice(),
				alpha = rgba.pop();

			if (includeAlpha) {
				rgba.push(~~( alpha * 255 ));
			}

			return "#" + jQuery.map(rgba, function (v, i) {

					// default to 0 when nulls exist
					v = ( v || 0 ).toString(16);
					return v.length === 1 ? "0" + v : v;
				}).join("");
		},
		toString: function () {
			return this._rgba[3] === 0 ? "transparent" : this.toRgbaString();
		}
	};
	color.fn.parse.prototype = color.fn;

	// hsla conversions adapted from:
	// http://www.google.com/codesearch/p#OAMlx_jo-ck/src/third_party/WebKit/Source/WebCore/inspector/front-end/Color.js&d=7&l=193

	function hue2rgb(p, q, h) {
		h = ( h + 1 ) % 1;
		if (h * 6 < 1) {
			return p + (q - p) * 6 * h;
		}
		if (h * 2 < 1) {
			return q;
		}
		if (h * 3 < 2) {
			return p + (q - p) * ((2 / 3) - h) * 6;
		}
		return p;
	}

	spaces.hsla.to = function (rgba) {
		if (rgba[0] == null || rgba[1] == null || rgba[2] == null) {
			return [null, null, null, rgba[3]];
		}
		var r = rgba[0] / 255,
			g = rgba[1] / 255,
			b = rgba[2] / 255,
			a = rgba[3],
			max = Math.max(r, g, b),
			min = Math.min(r, g, b),
			diff = max - min,
			add = max + min,
			l = add * 0.5,
			h, s;

		if (min === max) {
			h = 0;
		} else if (r === max) {
			h = ( 60 * ( g - b ) / diff ) + 360;
		} else if (g === max) {
			h = ( 60 * ( b - r ) / diff ) + 120;
		} else {
			h = ( 60 * ( r - g ) / diff ) + 240;
		}

		if (l === 0 || l === 1) {
			s = l;
		} else if (l <= 0.5) {
			s = diff / add;
		} else {
			s = diff / ( 2 - add );
		}
		return [Math.round(h) % 360, s, l, a == null ? 1 : a];
	};

	spaces.hsla.from = function (hsla) {
		if (hsla[0] == null || hsla[1] == null || hsla[2] == null) {
			return [null, null, null, hsla[3]];
		}
		var h = hsla[0] / 360,
			s = hsla[1],
			l = hsla[2],
			a = hsla[3],
			q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
			p = 2 * l - q,
			r, g, b;

		return [
			Math.round(hue2rgb(p, q, h + ( 1 / 3 )) * 255),
			Math.round(hue2rgb(p, q, h) * 255),
			Math.round(hue2rgb(p, q, h - ( 1 / 3 )) * 255),
			a
		];
	};


	each(spaces, function (spaceName, space) {
		var props = space.props,
			cache = space.cache,
			to = space.to,
			from = space.from;

		// makes rgba() and hsla()
		color.fn[spaceName] = function (value) {

			// generate a cache for this space if it doesn't exist
			if (to && !this[cache]) {
				this[cache] = to(this._rgba);
			}
			if (value === undefined) {
				return this[cache].slice();
			}

			var type = jQuery.type(value),
				arr = ( type === "array" || type === "object" ) ? value : arguments,
				local = this[cache].slice(),
				ret;

			each(props, function (key, prop) {
				var val = arr[type === "object" ? key : prop.idx];
				if (val == null) {
					val = local[prop.idx];
				}
				local[prop.idx] = clamp(val, prop);
			});

			if (from) {
				ret = color(from(local));
				ret[cache] = local;
				return ret;
			} else {
				return color(local);
			}
		};

		// makes red() green() blue() alpha() hue() saturation() lightness()
		each(props, function (key, prop) {
			// alpha is included in more than one space
			if (color.fn[key]) {
				return;
			}
			color.fn[key] = function (value) {
				var vtype = jQuery.type(value),
					fn = ( key === 'alpha' ? ( this._hsla ? 'hsla' : 'rgba' ) : spaceName ),
					local = this[fn](),
					cur = local[prop.idx],
					match;

				if (vtype === "undefined") {
					return cur;
				}

				if (vtype === "function") {
					value = value.call(this, cur);
					vtype = jQuery.type(value);
				}
				if (value == null && prop.empty) {
					return this;
				}
				if (vtype === "string") {
					match = rplusequals.exec(value);
					if (match) {
						value = cur + parseFloat(match[2]) * ( match[1] === "+" ? 1 : -1 );
					}
				}
				local[prop.idx] = value;
				return this[fn](local);
			};
		});
	});

	// add .fx.step functions
	each(stepHooks, function (i, hook) {
		jQuery.cssHooks[hook] = {
			set: function (elem, value) {
				var parsed, backgroundColor, curElem;

				if (jQuery.type(value) !== 'string' || ( parsed = stringParse(value) )) {
					value = color(parsed || value);
					if (!support.rgba && value._rgba[3] !== 1) {
						curElem = hook === "backgroundColor" ? elem.parentNode : elem;
						do {
							backgroundColor = jQuery.curCSS(curElem, "backgroundColor");
						} while (
						( backgroundColor === "" || backgroundColor === "transparent" ) &&
						( curElem = curElem.parentNode ) &&
						curElem.style
							);

						value = value.blend(backgroundColor && backgroundColor !== "transparent" ?
							backgroundColor :
							"_default");
					}

					value = value.toRgbaString();
				}
				elem.style[hook] = value;
			}
		};
		jQuery.fx.step[hook] = function (fx) {
			if (!fx.colorInit) {
				fx.start = color(fx.elem, hook);
				fx.end = color(fx.end);
				fx.colorInit = true;
			}
			jQuery.cssHooks[hook].set(fx.elem, fx.start.transition(fx.end, fx.pos));
		};
	});

	// detect rgba support
	jQuery(function () {
		var div = document.createElement("div"),
			div_style = div.style;

		div_style.cssText = "background-color:rgba(1,1,1,.5)";
		support.rgba = div_style.backgroundColor.indexOf("rgba") > -1;
	});

	// Some named colors to work with
	// From Interface by Stefan Petre
	// http://interface.eyecon.ro/
	colors = jQuery.Color.names = {
		aqua: "#00ffff",
		azure: "#f0ffff",
		beige: "#f5f5dc",
		black: "#000000",
		blue: "#0000ff",
		brown: "#a52a2a",
		cyan: "#00ffff",
		darkblue: "#00008b",
		darkcyan: "#008b8b",
		darkgrey: "#a9a9a9",
		darkgreen: "#006400",
		darkkhaki: "#bdb76b",
		darkmagenta: "#8b008b",
		darkolivegreen: "#556b2f",
		darkorange: "#ff8c00",
		darkorchid: "#9932cc",
		darkred: "#8b0000",
		darksalmon: "#e9967a",
		darkviolet: "#9400d3",
		fuchsia: "#ff00ff",
		gold: "#ffd700",
		green: "#008000",
		indigo: "#4b0082",
		khaki: "#f0e68c",
		lightblue: "#add8e6",
		lightcyan: "#e0ffff",
		lightgreen: "#90ee90",
		lightgrey: "#d3d3d3",
		lightpink: "#ffb6c1",
		lightyellow: "#ffffe0",
		lime: "#00ff00",
		magenta: "#ff00ff",
		maroon: "#800000",
		navy: "#000080",
		olive: "#808000",
		orange: "#ffa500",
		pink: "#ffc0cb",
		purple: "#800080",
		violet: "#800080",
		red: "#ff0000",
		silver: "#c0c0c0",
		white: "#ffffff",
		yellow: "#ffff00",
		transparent: [null, null, null, 0],
		_default: "#ffffff"
	};
})(jQuery);
